package IC.AST.SemanticAnalysis;

import java.util.LinkedList;

import IC.AST.*;
import IC.SymbolTables.SymbolTable;
import IC.SymbolTables.Types.*;
import IC.Error.*;
import IC.Error.Error.ErrorType;
import IC.SymbolTables.Types.ReturnType;;


class FillClassTablesVisitor extends  IC.AST.Visitor 
{
	private SymbolTable _symTable;
	private String _classID;
	
	public Object Visit(Program rootNode) { return VisitSons(rootNode); }

	public Object Visit(Library node) 
	{
		_symTable = node.GetSymbolTable();
		_classID = "Library";
		return VisitSons(node);
	}
	
	public Object Visit(ClassDeclaration classDec) 
	{
		_symTable = classDec.GetSymbolTable();
		_classID = classDec.GetID();
		
		SetThisPointer();
		
		return VisitSons(classDec);
	}
	
	private void SetThisPointer()
	{
		_symTable.SetSymbol("this",
				new Symbol(Symbol.Kind.FIELD,
					new IC.SymbolTables.Types.ClassType(
							_classID, 
							_symTable)
				, false));
	}
	
	public Object Visit(ClassField field) 
	{	
		if (_symTable.GetSymbol(field.GetID(), true) == null)
		{
			_symTable.SetSymbol(
					field.GetID(), 
					new Symbol(
							Symbol.Kind.FIELD,
							(IC.SymbolTables.Types.Type)field.GetType(),
							false));

			field.SetSymbolTable(_symTable);
		}
		else
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, "Illegal field redifenition in class=" + _classID, field.GetInterval()));
		
		return null;
	}
	
	public Object Visit(Method method)
	{
		MethodType methodType = CreateMethodType(method.GetFormals(), method.GetReturnType());
		method.SetType(methodType);

		if (!CheckOveridingAndHiding(method, methodType))
			return null;
		
		_symTable.SetSymbol(method.GetID(), new Symbol(Symbol.Kind.METHOD, methodType, method.IsStatic()));
		
		return null;
	}
	
	public Object Visit(LibraryMethod method) 
	{ 
		if (!CheckLocalDefenition(method.GetID(), method.GetInterval(), true))
			return null;
		
		_symTable.SetSymbol(method.GetID(), 
				new Symbol(Symbol.Kind.METHOD,  
						CreateMethodType(method.GetFormals(), method.GetReturnType()), true));
		
		return null;
	}
	
	private boolean CheckLocalDefenition(String methodID, IC.AST.Interval interval, boolean isInLib)
	{
		if (_symTable.SearchCurrentTable(methodID) != null)
		{
			ErrorStack.Push(new ErrorStringIntervaled(ErrorType.Semantic, 
								String.format("Illegal function = '%s' redifinition (2 members with the same name in class = '%s').", methodID, _classID)
								, interval, isInLib));
			return false;
		}
		return true;
	}
	
	private boolean CheckOveridingKind(Symbol lastDef, Method method)
	{
		if (lastDef.GetKind() != Symbol.Kind.METHOD)
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(ErrorType.Semantic, 
							String.format("Method = '%s' in class = '%s' hiding inharited class field.", method.GetID(), _classID), 
							method.GetInterval()));
			return false;
		}
		return true;
	}
	
	private boolean CheckOverloadingStatics(Symbol lastDef, Method method)
	{
		if (method.IsStatic() != lastDef.IsStatic())
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(ErrorType.Semantic, 
							String.format("Static none static conflict between inherited methods = '%s' in class = '%s'.", method.GetID(), _classID)
							, method.GetInterval()));
			return false;
		}
		return true;
	}
	
	private boolean CheckOverloadingTypes(Symbol lastDef, Method method, MethodType methodType)
	{
		if (!methodType.IsInstanceOf(lastDef.GetType()))
		{
			ErrorStack.Push(
					new ErrorStringIntervaled(ErrorType.Semantic, 
						String.format("can't overide function, uncompetable return type/parameters in methods = '%s' in class = '%s'.", method.GetID(), _classID)
						, method.GetInterval()));
			return false;
		}
		return true;
	}
	
	private boolean CheckOveridingAndHiding(Method method, MethodType methodType)
	{
		if (!CheckLocalDefenition(method.GetID(), method.GetInterval(), false))
			return false;
		
		Symbol lastDef = _symTable.GetSymbol(method.GetID(), true);
		
		return (lastDef == null) || 
			(CheckOveridingKind(lastDef, method) && CheckOverloadingStatics(lastDef, method) && CheckOverloadingTypes(lastDef, method, methodType));
	}
	
	private MethodType CreateMethodType(LinkedList<Formal> formals, IC.AST.Type returnType)
	{
		IC.SymbolTables.Types.InstanceType[] params = new IC.SymbolTables.Types.InstanceType[formals.size()];
		
		int i = 0;
		for (Formal formal : formals)
			params[i++] = (IC.SymbolTables.Types.InstanceType)formal.GetType();
		
		return new MethodType(
						(returnType == null) 
						?
							new VoidType() 
						: 
							(ReturnType)returnType.GetType(), 
						params);
	}
}
